﻿#region

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Tateeda.ClinicalStudy.Base.Modules.Rules.Mapping;

#endregion

#region - Legal -

///<legal>
/// Copyright © 2011, Tateeda Media Network
/// All rights reserved. http://tateeda.com
///
/// Redistribution and use in source and binary forms, with or without
/// modification, are permitted provided that the following conditions
/// are met:
///
/// - Redistributions of source code must retain the above copyright
/// notice, this list of conditions and the following disclaimer.
///
/// - Neither the name of the Tateeda Media Network, nor the names of its
/// contributors may be used to endorse or promote products
/// derived from this software without specific prior written
/// permission.
///
/// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
/// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
/// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
/// FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
/// COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
/// INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES INCLUDING,
/// BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
/// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
/// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
/// LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
/// ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
/// POSSIBILITY OF SUCH DAMAGE.
///</legal>

#endregion - Legal -

namespace Tateeda.ClinicalStudy.Base.Modules.Rules.Flowchart {

    public class Flowchart<T, R> {

        public Flowchart() {
            Shapes = new List<Shape<T, R>>();
        }

        public List<Shape<T, R>> Shapes { get; set; }

        public void Validate() {
            CheckForInvalidDestinations();
            CheckForDuplicateNames();
        }

        public EvaluationResults<T, R> Evaluate(string MeasureName, T data) {
            Shape<T, R> shape = Shapes[0];
            var visitedShapes = new List<Shape<T, R>> { shape };

            while (true) {
                Arrow<T> arrow = shape.Arrows.FirstOrDefault(t => t.Rule(data));
                if (arrow != null) {
                    if (arrow.Action != null) {
                        arrow.Action(data);
                    }
                    shape = Shapes.Where(s => s.Name == arrow.PointsTo).Single();
                    visitedShapes.Add(shape);
                } else {
                    break;
                }
            }

            return ComputeEvaluationResults(MeasureName, visitedShapes);
        }

        private EvaluationResults<T, R> ComputeEvaluationResults(string MeasureName, List<Shape<T, R>> visitedShapes) {
            EvaluationResults<T, R> results = new EvaluationResults<T, R>();
            Shape<T, R> lastShape = visitedShapes[visitedShapes.Count - 1];
            //results.MeasureName = MeasureName;
            results.Result = lastShape.Result;
            results.RequiredFields = visitedShapes.Where(s => s.RequiredField != null)
                .Select(s => s.RequiredField)
                .Distinct(PropertySpecifier<T>.Comparer)
                .ToList();
            return results;
        }

        private void CheckForDuplicateNames() {
            var duplicateShapes = Shapes.GroupBy(s => s.Name).Where(g => g.Count() > 1);
            if (duplicateShapes.Count() > 0) {
                string message = "The following shape names are duplicated: " +
                                 duplicateShapes.Aggregate(new StringBuilder(), (sb, s) => sb.Append(s.Key + " "),
                                                           sb => sb.ToString());

                throw new InvalidOperationException(message);
            }
        }

        private void CheckForInvalidDestinations() {
            var names = Shapes.Select(s => s.Name);
            var problemTransitions = Shapes.SelectMany(s => s.Arrows)
                .Where(t => !names.Contains(t.PointsTo));
            if (problemTransitions.Count() > 0) {
                string message = "The following destination names are invalid: " +
                                 problemTransitions.Aggregate(new StringBuilder(),
                                                              (sb, t) => sb.Append(t.PointsTo + " "),
                                                              sb => sb.ToString());

                throw new InvalidOperationException(message);
            }
        }
    }
}